# dgr.py - A DGR.
# Copyright 2009 Rob Myers <rob@robmyers.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

################################################################################
# Imports
################################################################################
import fetch_url
import simplejson
import logging
import random
import re
import datetime

import feedparser
import sys
import traceback
import twitter
import urllib2
import yaml

import tweet

from dgrWalking import dgrWalking
from google.appengine.api import urlfetch 
from googlemaps import GoogleMaps
from google.appengine.ext import db

try:
    from google.appengine.api.taskqueue import Task
except:
    from google.appengine.api.labs.taskqueue import Task

################################################################################
# dgr - original code created by rob - extended by nick
################################################################################
   
class Dgr:

    #def __init__ (self, config_path, state, log, server_config, live=True):
    def __init__ (self, config_path):
        '''Set up the dgr and deserialize the configuration.'''
        self.tweet_retries = 5
        self.config_path = config_path
        self.twitter_api = False
        self.current_time = None
        self.current_keywords = None
        self.publishers = []
        self.posterous_username = None
        self.configure ()
        self.twitter_api = False
        self.IsWalking = False
        self.configure ()

    def configure (self):
        '''Deserialize the configuration.'''
        # Load configuration
        yaml_stream = file (self.config_path, 'r')
        config = yaml.safe_load (yaml_stream)
        self.twitter_username = config["twitter_username"]
        self.twitter_password = config["twitter_password"]
        #add the user to the user list - this will first check if it exists - we want this data to be distinct
        if 'posterous_username' in config:
            self.posterous_username = config["posterous_username"]
            self.posterous_password = config["posterous_password"]
        self.greeting_message = "Hi!"
        if ("greeting" in config):
            self.greeting_message = config["greeting"]
        
        self.wff_search = config["wff_search"]
        self.emotion_keywords = config["emotion_keywords"]
        self.time_keywords = config["time_keywords"]
        if (("weekend_time_keywords" in config) and
            (str (config["weekend_time_keywords"]).strip() != "")):
            self.weekend_time_keywords = config["weekend_time_keywords"]
        else:
            self.weekend_time_keywords = config["time_keywords"]
        self.character_keywords = config["character_keywords"]
        if 'walking' in config:
            self.IsWalking = True
        self.ensure_time_keywords_keys_are_ints ()

    def ensure_time_keywords_keys_are_ints (self):
        '''Make sure the time keyword keys are ints, not strings'''
        for key in self.time_keywords.keys ():
            value = self.time_keywords[key]
            del self.time_keywords[key]
            self.time_keywords[int (key)] = value

    def time_of_day_keywords (self, now):
        '''Get the keyword list for the time of day'''
        # datetime hours go 0..23
        hour = now.hour
        current_keywords = self.time_keywords
        # ISO weekdays go 1..7
        if (now.isoweekday () > 5):
            current_keywords = self.weekend_time_keywords
        times = current_keywords.keys ()
        times.sort ()
        keywords = []
        for time_start in times:
            if (hour > time_start):
                keywords = current_keywords[time_start]
        logging.info ("Time of day keywords: " + ", ".join (keywords))
        return keywords

    def emotions_keywords (self, emotions):
        '''Get the keyword list for all the emotions'''
        # Or just for the first match?
        keywords = []
        for emotion in emotions:
            if (emotion in self.emotion_keywords):
                keywords += self.emotion_keywords[emotion]
        logging.info ("Emotions keywords: " + ", ".join (keywords))
        return keywords

    def fetch_wff_emotions (self):
        '''Fetch the emotion list based on the dgrs wff query'''
        logging.debug ("WFF search: " + self.wff_search)
        emotions = []
        try:
            # 10 is max deadline
            search_results = fetch_url.fetch_url(self.wff_search)
            #logging.debug ("WFF search returned: " + search_results)
            emotions = re.findall (r"feeling=\"([^\"]+)\"", search_results)
            # Ensure unique terms
            emotions = list (set (emotions))
        except Exception, err:
            logging.info (type(err))
            logging.info (err)
            traceback.print_exc(file=sys.stdout)
        logging.info ("WFF emotions: " + ", ".join (emotions))
        return emotions

    def generate_emotion_keywords (self, now):
        '''Combine the keywords into a usable list'''
        wffs = self.fetch_wff_emotions ()
        emotions = self.emotions_keywords (wffs)
        times = self.time_of_day_keywords (now)
        random.shuffle (wffs)
        random.shuffle (emotions)
        random.shuffle (times)
        random.shuffle (self.character_keywords)
        logging.info ("Character keywords: " + 
                       ", ".join (self.character_keywords))
        keywords = wffs[:10] + emotions[:10] +times[:10] + \
        self.character_keywords[:10]
        random.shuffle (keywords)
        keywords = keywords[:10]
        logging.info ("Combined keywords: " + ", ".join (keywords))
        return keywords

    def post_twitter (self, emotions):
        '''Retweet a tweet that matched one of the emotions'''
        dgr_task = Task(url='/search-twitter', method='POST',
                        params={'dgr-yaml-path': self.config_path,
                                'emotions': simplejson.dumps(emotions)})
        dgr_task.add('search-twitter-queue')

    def update_state (self):
        '''Update the dgr's current internal state'''
        self.current_time = datetime.datetime.utcnow ()
        self.current_thoughts = \
            self.generate_emotion_keywords (self.current_time)

    def store_state (self):
        '''Store the dgr's current state in the datastore'''
        pass

    def publish_state (self):
        '''Send the current state to the dgr's web 2.0 accounts'''
        assert(self.current_thoughts != None)
        self.post_twitter (self.current_thoughts)
    
        
    def connect_to_twitter (self):
        '''Make twitter connection'''
        logging.debug ("Connecting to Twitter.")
        if (not self.twitter_api):
            self.twitter_api = twitter.Api (username = self.twitter_username,
                                            password = self.twitter_password)
        logging.debug ("Connected to Twitter.")

    def tweetGEO (self, message, lat=None, lng=None):
        '''Send a tweet through the dgr's Twitter account'''
        tweeted = False
        logging.debug ("Tweeting '" + message + "'.")
        self.connect_to_twitter ()
        # If Twitter returns 408 (timeout) or 503 (unavail), try a few times
        for i in range (1, self.tweet_retries):
            try:
                self.twitter_api.PostUpdateGEO (message, None, lat, lng)
                tweeted = True
                break
            except urllib2.HTTPError, hterr:
                logging.info (type(hterr))
                logging.info (hterr)
                if ((hterr.errno != 408) and (hterr.errno != 503)):
                    print (type(hterr))
                    print (hterr)
                    traceback.print_exc(file=sys.stdout)
        logging.debug ("Tweeted.")
        return tweeted

    def tweet (self, message):
        '''Send a tweet through the dgr's Twitter account'''
        tweeted = False
        logging.debug ("Tweeting '" + message + "'.")
        self.connect_to_twitter ()
        # If Twitter returns 408 (timeout) or 503 (unavail), try a few times
        for i in range (1, self.tweet_retries):
            try:
                self.twitter_api.PostUpdate (message)
                tweeted = True
                break
            except urllib2.HTTPError, hterr:
                logging.info (type(hterr))
                logging.info (hterr)
                if ((hterr.errno != 408) and (hterr.errno != 503)):
                    print (type(hterr))
                    print (hterr)
                    traceback.print_exc(file=sys.stdout)
        logging.debug ("Tweeted.")
        return tweeted


    def character_keywords (self):
        '''Get the keyword list for all the emotions'''
        # Or just for the first match?
        keywords = []
        for emotion in emotions:
            if (emotion in self.emotion_keywords):
                keywords += self.emotion_keywords[emotion]
        logging.info ("Emotions keywords: " + ", ".join (keywords))
        return keywords

    def retweet_emotional_state (self, emotions):
        '''Retweet a tweet that matched one of the emotions or keywords'''
        logging.debug ("Retweeting matches for " + ", ".join (emotions) + ".")
        tweeted = False
        usable_tweets = \
            tweet.twitter_any_search (emotions,
                                      exclude_retweets=True,
                                      exclude_users=[self.twitter_username])
        if (len (usable_tweets) > 0):
            # Get the first usable tweet
            first_tweet = usable_tweets[0]
            # Format it up into a retweet
            retweet = first_tweet.to_retweet ()
            logging.info ("Retweet: " + retweet)
            # And call the twitter API
            tweeted = self.tweet (retweet)
            logging.debug ("Retweeted.")
        else:
            logging.debug ("No usable tweets to retweet.")
        return tweeted
    
    #FIXME- Re-implement mention handling!!!

    def walk(self, searchTerms):
        #uses the dgrWalking class to manage setting locations
        #dgrWalking will use the Yaml file plus the tweet search terms to obtain searchTerms to be used to walk
        #or it will use the routine if there is one.
        
        #we should determine based on time of day whether we should be going anywhere
        
        #instantiate dgrWalking class
        logging.debug("instantiate dgrWalking " + self.config_path)
        self.walking = dgrWalking(self.config_path)
        
        #check user exists in our list - if not add the user to the datastore        
        self.walking.addTUser(self.twitter_username, self.twitter_password)

        #set droid tweet term
        tweetterms = ["floating around by", "whirring outside", "recording round the corner from", "feeling bored in the vacinity of", "HAL there's nothing happening here at"]
        random.shuffle(tweetterms)
        #connect to twitter
        self.connect_to_twitter ()
        
        destinations = self.walking.walk(searchTerms, self.twitter_api)
        #getDestination - returns dict of destinations from google local search
        #destinations = dgrWalking.getDestination()
        logging.debug ('destination type = ' + str(type(destinations)))
        if (type(destinations) is str):
            #located = self.tweetGEO (destinations)
            
            #get the right address - home or work
            address = []
            address = self.walking.getAddress(str(self.walking.isHomeOrWork()))
            logging.debug ('we have ' + str(len(address)) + ' adresses' + str(address[0]))
            zoom_level = 0
            street_address = address[0]['streetAddress']
            title = address[0]['titleNoFormatting']
            region = address[0]['region']
            city = address[0]['city']
            lat = address[0]['lat']
            lng = address[0]['lng']
            url = '%s&reviews=1' % address[0]['url']
            phone = ''
            fulladdress = ''
            for addressline in address[0]['addressLines']:
                fulladdress = fulladdress + addressline + ' '
            try:
                located = self.tweetGEO ("whir click... " + tweetterms[0] + ' ' + destinations, lat=lat, lng=lng)                        
            except Exception, err:
                logging.info ('problem with twitter' + str(err))
                pass
            logging.info ('lat = ' + str(lng))
            logging.info ("Destination: " + fulladdress)
        elif len(destinations) == 0:
            #get the right address - home or work
            logging.debug('we had no destinations')
            address = []
            address = self.walking.getAddress(str(self.walking.isHomeOrWork()))
            logging.debug('we are now here')
            #logging.debug ('we have ' + str(len(address)) + ' adresses' + str(address[0]))
            #logging.debug ('' .join(address[0]))
            zoom_level = 0
            street_address = address[0]['streetAddress']
            title = address[0]['titleNoFormatting']
            region = address[0]['region']
            city = address[0]['city']
            lat = address[0]['lat']
            lng = address[0]['lng']
            url = '%s&reviews=1' % address[0]['url']
            phone = ''
            fulladdress = ''
            for addressline in address[0]['addressLines']:
                fulladdress = fulladdress + addressline + ' '
                        
            #located = self.tweetGEO ("My destination " + address[0]['titleNoFormatting'] + ' ' + address[0]['streetAddress'])
            try:
                located = self.tweetGEO ("beep... " + tweetterms[0] + ' ' + destinations[0]['titleNoFormatting'], lat=lat, lng=lng)
            except Exception, err:
                logging.info ('problem with twitter' + str(err))
                pass
            logging.info ('lat = ' + str(lng))
            logging.info ("Elif Destination: " + fulladdress)
            
        else:
            logging.info (str(len(destinations)))
            if (destinations):
                # Get the first destination
                #will need to think about distance/time to arrive - but do that later.
    			
                destination = []
                #set up destination data
          
                zoom_level = 0
                street_address = destinations[0]['streetAddress']
                title = destinations[0]['titleNoFormatting']
                region = destinations[0]['region']
                city = destinations[0]['city']
                lat = destinations[0]['lat']
                lng = destinations[0]['lng']
                url = '%s&reviews=1' % destinations[0]['url']
                phone = ''
                    
                #addresslines = '' .join(destinations[0]['addressLines'])
                
                fulladdress = ''
                for addressline in destinations[0]['addressLines']:
                    fulladdress = fulladdress + addressline + ' '
                            
                logging.info ('lat = ' + str(lng))
                logging.info ("Destination: " + fulladdress)

                
            #tweet location
            
            #on succussful tweet update datamodel with new destination
                
                
                # And call the twitter API
                
                #located = self.tweetGEO ("My destination " + destinations[0]['titleNoFormatting'] + ' ' + destinations[0]['streetAddress'], lat=lat, lng=lng)
            try:
                located = self.tweetGEO ("beep... " + tweetterms[0] + ' ' + destinations[0]['titleNoFormatting'], lat=lat, lng=lng)
            except Exception, err:
                logging.info ('problem with twitter' + str(err))
                pass

        self.walking.SaveDestination(lat, lng, title, url, street_address, region, city, '1', phone, fulladdress)
        
		
    def run (self):
        '''Run the dgr'''
        now = datetime.datetime.utcnow ()
        logging.info("Running @" + self.twitter_username)
        logging.info("Run Time - " + str (now))
        thoughts = self.generate_emotion_keywords (now)
        #tweeted = self.retweet_emotional_state (thoughts)
        tweeted = False
        if self.IsWalking:
            located = self.walk(self.fetch_wff_emotions ())
		#get location information here
		
        logging.info ("--------------------------------------------------------------------------------")
        return tweeted
            
    def ensure_loctime_keywords_keys_are_ints (self):
        '''Make sure the time keyword keys are ints, not strings'''
        for key in self.time_location_keywords.keys ():
            value = self.time_location_keywords[key]
            del self.time_location_keywords[key]
            self.time_location_keywords[int (key)] = value
			
    def get_persistent_value (self, key, default=False):
        "Get a value that has hopefully survived between runs."
        result = default
        if (key in self.persistent_state):
            result = self.persistent_state[key]
        return result

    def set_persistent_value (self, key, value):
        "Set a value that will hopefully survive between runs."
        self.persistent_state[key] = value

